
from PyQt4 import QtGui, QtOpenGL, QtCore, Qt

import graph_items
import rsc

#
#--- Base for manipulators
#

# This is used to convert PyQt4.QtCore.MouseButtons
# object to a hashable and comparable type.
# I guess it is not needed in C++, but the python
# binding are not strong enought...
_BUTTONS_TYPE = int

class GraphicsViewManip( QtCore.QObject ):
    '''
    enableKey:
        single key like QtCore.Qt.Key_Alt
    
    enableCursor:
        int or string like "cursor_t" or QtCore.Qt.OpenHandCursor
    
    buttons:
        list of single or combined buttons like:
            ( QtCore.Qt.LeftButton, QtCore.Qt.LeftButton | QtCore.Qt.MidButton, )
    
    cursors:
        list of cursor names, int or tuple like:
            ( "cursor_add", ("cursor_t", hotX, hotY), QtCore.Qt.OpenHandCursor, )
            
    name: string
    
    others are function signed with one parameter: event
    
    '''
    @staticmethod
    def GetCursor( nameOrEnumOrTuple ):
        if nameOrEnumOrTuple is None:
            c = QtCore.Qt.ArrowCursor
        elif isinstance( nameOrEnumOrTuple, (list,tuple) ):
            c = rsc.GetCursor(*nameOrEnumOrTuple)
        else:
            c = rsc.GetCursor(nameOrEnumOrTuple)
        return c
    
    def __init__( 
            self, graphicsView, 
            enableKey=None, enableCursor=None, 
            buttons=(),
            cursors=(),
            onActivate=None, onMouseMove=None, onDesactivate=None,
            onEnable=None, onDisable=None,
            disableInteractive=True, # usefull???
            name=None,
        ):
        QtCore.QObject.__init__( self, graphicsView )
        
        if name is not None:
            self.setObjectName( name )
        
        self._view = graphicsView
        
        self._enableSlot = onEnable
        self._disableSlot = onDisable
        self._activateSlot = onActivate
        self._mouseMoveSlot = onMouseMove
        self._desactivateSlot = onDesactivate
        
        self._disableInteractive = disableInteractive
        self._wasInterative = graphicsView.isInteractive()
        
        self._enableKey = enableKey
        self._activeButtons = buttons
        self._enabled = enableKey is None
        self._active = False
        self._activeButton = _BUTTONS_TYPE(QtCore.Qt.NoButton)
        
        self._buttonCursors = {
            _BUTTONS_TYPE(QtCore.Qt.NoButton): self.GetCursor(enableCursor),
        }
        self._buttonCursors.update(
            zip( 
                [_BUTTONS_TYPE(b) for b in buttons],
                [self.GetCursor(c) for c in cursors]
            )
        )
        
        self._activeCursor = QtGui.QCursor(QtCore.Qt.OpenHandCursor)
        self.panCursor = QtGui.QCursor(QtCore.Qt.ClosedHandCursor)
        self.zoomCursor = QtGui.QCursor(QtCore.Qt.SizeAllCursor)
        
        self.last_scale = None
        
    def getView( self ):
        return self._view
    
    def enable( self ):
        self._activeButton = _BUTTONS_TYPE(QtCore.Qt.NoButton)
        self._enabled = True
        self.setCursor()
    
    def isEnabled( self ):
        return self._enabled
        
    def disable( self ):
        self._activeButton = _BUTTONS_TYPE(QtCore.Qt.NoButton)
        self._enabled = False
        self.setCursor()
    
    def activate( self, button ):
        self._active = True
        self._activeButton = button
        self.setCursor()
        self._view.setActiveManip( self )
        
    def isActive( self ):
        return self._active
    
    def desactivate( self ):
        self._active = False
        self._activeButton = _BUTTONS_TYPE(QtCore.Qt.NoButton)
        self.setCursor()
        self._view.setActiveManip(None)
    
    def setCursor( self ):
        c = self._buttonCursors[self._activeButton]
        if self._enabled and c is not None:
            # use global cursor override instead of
            self.getView().setCursor( c )
            # because it is stacked:
            #QtGui.QApplication.setOverrideCursor(c)
        else:
            self.getView().unsetCursor()
            #QtGui.QApplication.restoreOverrideCursor()
    
    def storeStates(self, pos ):
        view = self.getView()
        self.start_vpos = QtCore.QPoint( pos )
        self.vcenter = QtCore.QPoint(view.width()/2, view.height()/2)
        self.start_vcenter_offset = self.start_vpos - self.vcenter
        self.start_pos = view.mapToScene( pos )
        self.center = view.mapToScene( self.vcenter )
        self.start_center_offset = self.start_pos - self.center
        if self.last_scale is not None:
            self.start_scale = self.last_scale
        else:
            self.start_scale = view.scene().root().transform().map( 1000.0, 1000.0 )[0]/1000.0
        self.start_trans = view.transform()
        self.last_pos = view.mapToScene( pos )
        self.last_vpos = QtCore.QPoint( pos )
        self.view_center = view.mapToScene( view.width()/2, view.height()/2 )
    
    def eventFilter( self, obj, event ):
        view = self.getView()
        if obj != view:
            return False
        if view.activeManip() is not None and view.activeManip() != self:
            return False
        
        if event.type() == QtCore.QEvent.KeyPress:
            if event.key() == self._enableKey:
                self.enable()
                self.OnEnable(event)
                return False # allow further activation
        
        if not self.isEnabled():
            return False
        
        elif event.type() == QtCore.QEvent.MouseButtonPress:
            if event.buttons() in self._activeButtons:
                if not self.AllowActivation( event ):
                    return False
                self.storeStates( event.pos() )
                self.activate( _BUTTONS_TYPE(event.buttons()) )
                self.OnActivate(event)
                if self._disableInteractive:
                    self._wasInterative = self.getView().isInteractive()
                    self.getView().setInteractive( False )
                view.grabMouse()
                return True # this one is for me !
        
        elif event.type() == QtCore.QEvent.MouseMove:
            if self.isActive():
                return self.OnMouseMove( event )
        
        elif event.type() == QtCore.QEvent.MouseButtonRelease:
            if self.isActive():
                self.OnDesactivate(event)
                self.desactivate()
                self.storeStates( event.pos() )
                view.releaseMouse()
                if self._disableInteractive:
                    self.getView().setInteractive( self._wasInterative )
                return False # allow further activation
        
        if event.type() == QtCore.QEvent.KeyRelease:
            if event.key() == self._enableKey and self._enableKey is not None:
                self.OnDisable(event)
                self.disable()
                return False # allow further activation
        
        return False
    
    def ForceStop( self, event=None ):
        '''
        Called internally to disable the manipulator (i.e. when the enabling
        key is released)
        
        External use this to force disabling of an enabled manipulator.
        (don't just call disable or desactivate!)
        '''
        # NB: this code is almost a copy of the end of eventFilter()
        # -> keep it sync
        if self.isActive():
            view = self.getView()
            self.OnDesactivate(None)
            self.desactivate()
            self.storeStates(
                view.mapFromGlobal(QtGui.QCursor.pos())
            )
            self._view.releaseMouse()
            if self._disableInteractive:
                view.setInteractive( self._wasInterative )
            return False # allow further activation
        
        # to avoid desactivating
        # a filter with enableKey == None:
        if self._enableKey is not None:
            self.OnDisable(event)
            self.disable()

    def AllowActivation( self, event ):
        '''
        This provide context sensitive activation control for this
        event handler.
        Default imp is to allow the event (key and mouse button are alredy
        satisfyed before this call.)
        '''
        return True
    
    def OnEnable( self, event ):
        '''
        This event handler is triggered when the filter enables itself.
        Default imp is to run the onEnable function passed in the constructor.
        '''
        if self._enableSlot is not None:
            self._enableSlot(event)
    
    def OnDisable( self, event=None ):
        '''
        This event handler is triggered when the filter disables itself.
        Default imp is to run the onDisable function passed in the constructor.
        The event is None if the disabling is caused b a ForceStop() call.
        '''
        if self._disableSlot is not None:
            self._disableSlot(event)
    
    def OnActivate( self, event ):
        '''
        This event handler is triggered when the filter activates itself.
        Default imp is to run the onActivate function passed in the constructor.
        '''
        if self._activateSlot is not None:
            self._activateSlot(event)
    
    def OnMouseMove( self, event ):
        '''
        This event handler is triggered when the filter filter accepts a mouse move.
        Default imp is to run the onMouseMove function passed in the constructor
        and return its return value.
        
        The result indicates if the event should continue its propagation:
            True to stop it
            False to let him continue
            
        '''
        if self._mouseMoveSlot is not None:
            return self._mouseMoveSlot(button, event)
        return False
    
    def OnDesactivate( self, event=None ):
        '''
        This event handler is triggered when the filter desavtivates itself.
        Default imp is to run the onDesactivate function passed in the constructor.
        The event is None if the desactivation is caused b a ForceStop() call.
        '''
        if self._desactivateSlot is not None:
            self._desactivateSlot(event)
    
    def GetMovable( self, pos ):
        '''
        Utility function to retreive the first movable item at (viewport) pos.
        '''
        view = self.getView()
        items = view.items(pos)
        for i in items:
            if isinstance( i, graph_items.AssetItem ):
                return i
        return None
    
class GraphicsViewBoxManip( GraphicsViewManip ):
    def __init__( self, *args, **kwargs ):
        GraphicsViewManip.__init__(
            self, *args, **kwargs
        )
        self.rectItem = None
        self.pen = QtGui.QPen(
            QtGui.QBrush( QtGui.QColor('#FFFFFF' ) ), 1#this was slow:, QtCore.Qt.DotLine,
        )
        self.brush = QtGui.QBrush(
            QtGui.QColor( 255, 255, 255, 32 )
        )
    
    def setPen( self, pen ):
        self.pen = pen
    
    def setBrush( self, brush ):
        self.brush = brush
    
    def OnActivate( self, event ):
        self.rectItem = self.getView().scene().addRect(
            QtCore.QRectF( self.start_pos, QtCore.QSizeF(5,5) ),
            self.pen, self.brush
        )
        self.rectItem.setZValue( 100 )
    
    def OnMouseMove( self, event ):
        view = self.getView()
        pos = view.mapToScene( event.pos() )
        self.rectItem.setRect(
            QtCore.QRectF(
                min( self.start_pos.x(), pos.x() ),
                min( self.start_pos.y(), pos.y() ), 
                abs( self.start_pos.x() - pos.x() ),
                abs( self.start_pos.y() - pos.y() ), 
            )
        )
        return True
    
    def OnDesactivate( self, event ):
        items = self.getView().scene().collidingItems( self.rectItem )
        #TODO: too ugly for me:
        items = set( [i._selecter for i in items if hasattr(i,'_selecter')] )
        rect = self.rectItem.rect()
        self.getView().scene().removeItem( self.rectItem )
        self.rectItem = None
        self.OnAfterBox( rect, items, event )
    
    def OnAfterBox( self, rect, items, event ):
        raise NotImplementedError

#
#--- Implemented Manipulators
#
class PanManip( GraphicsViewManip ):
    def __init__( self, graphicsView ):
        GraphicsViewManip.__init__(
            self, graphicsView,
            enableKey=QtCore.Qt.Key_Alt,
            enableCursor=QtCore.Qt.OpenHandCursor, 
            buttons=(QtCore.Qt.LeftButton,),
            cursors=(QtCore.Qt.ClosedHandCursor,), 
            name="Pan_Manipulator"
        )
    
    def OnMouseMove( self, event ):
        return self.OnMouseMove_view(event)

    def OnMouseMove_root( self, event ):
        view = self.getView()
        pos = view.mapToScene( event.pos() )
        dist = pos - self.last_pos
        view.scene().root().moveBy( dist.x(), dist.y() )
        self.last_pos = QtCore.QPointF(pos)
        return True

    def OnMouseMove_view( self, event ):
        view = self.getView()
        pos = view.mapToScene( event.pos() )
        dist = pos - self.start_pos
        trans = self.start_trans.translate( dist.x(), dist.y() )
        view.setTransform( trans )
        return True

class ZoomManip( GraphicsViewManip ):
    def __init__( self, graphicsView ):
        GraphicsViewManip.__init__(
            self, graphicsView,
            enableKey=QtCore.Qt.Key_Alt,
            enableCursor=None,
            buttons=(QtCore.Qt.RightButton,),
            cursors=(("zoomFit",5,5),), 
            name="Zoom_Manipulator"
        )
    
    def OnMouseMove( self, event ):
        return self.OnMouseMove_root(event)

    def OnMouseMove_root( self, event ):
        view = self.getView()
        dist = event.pos() - self.start_vpos
        scale = max( 0.1, self.start_scale + (dist.x() / 1000.0) )
        print dist.x(), scale, self.start_scale
        zx, zy = self.start_pos.x(), self.start_pos.y()
        view.scene().root().setTransform(
            QtGui.QTransform().translate( -zx, -zy ).scale( scale, scale ).translate( zx, zy )
        )
#        view.scene().root().scale( scale, scale )
        self.last_scale = scale
        return True
    
    def OnMouseMove_view( self, event ):
        view = self.getView()
        dist = event.pos() - self.last_vpos
        if dist.x() > 0:
            f = 1.1
        else:
            f = 0.9
        f = max( 0.1, 1 + ( dist.x() / 100.0 ) )
        zx, zy = self.start_center_offset.x(), self.start_center_offset.y()
        print zx, zy
        view.translate( -zx, -zy )
        view.setTransform( view.transform().scale(f,f) )
        view.translate( *view.transform().map( zx, zy ) )
        #trans = self.start_trans.translate( -zx, -zy ).scale( f, f )#.translate( zx, zy )
        self.last_vpos = QtCore.QPoint(event.pos())
        return True


class MoveManip( GraphicsViewManip ):
    MODE_SINGLE = 0
    MODE_UPS    = 1
    MODE_DOWNS  = 2
    def __init__( self, graphicsView, mode=None ):
        if mode is None:
            mode = MoveManip.MODE_SINGLE
        k, c = {
            MoveManip.MODE_SINGLE: (None, QtCore.Qt.PointingHandCursor),
            MoveManip.MODE_UPS:    (QtCore.Qt.Key_Shift,('cursor_plus',0,0)),
            MoveManip.MODE_DOWNS:  (QtCore.Qt.Key_Control,('cursor_t',0,0)),
        }[mode]
        GraphicsViewManip.__init__(
            self, graphicsView,
            enableKey=k,
            enableCursor=c,
            buttons=(QtCore.Qt.LeftButton,),
            cursors=(c,), 
            name="Move_Manipulator"
        )
        self.mode = mode
        self.toMove = []
    
    def AllowActivation( self, event ):
        movable = self.GetMovable(event.pos())
        #TODO: this is uuuuuuglyyy !!! use a selection model...
        selected = graph_items.AssetItem.getSelected()
        if movable not in selected:
            graph_items.AssetItem.clearSelect()
            movable.setSelected(True)
            selected = movable.getSelected()
        self.toMove = []
        [ self.toMove.extend(i.GetMovables( self.mode )) for i in selected ]
        self.toMove = set( self.toMove )
        return self.toMove and True
        return False
    
    def OnMouseMove( self, event ):
        view = self.getView()
        pos = view.mapToScene( event.pos() )
        dist = pos - self.last_pos
        [ i.moveBy( dist.x(), dist.y() ) for i in self.toMove ]
        self.last_pos = QtCore.QPointF(pos)
        return True
    
    def OnDesactivate( self, event ):
        self.toMove = []

class SelectManip( GraphicsViewBoxManip ):
    MODE_STD = 0
    MODE_ADD = 1
    MODE_TGL = 2
    def __init__( self, graphicsView, mode=None ):
        if mode is None:
            mode = SelectManip.MODE_STD
        k, c = {
            SelectManip.MODE_STD: (None, QtCore.Qt.ArrowCursor),
            SelectManip.MODE_ADD: (QtCore.Qt.Key_Shift,('cursor_plus',0,0)),
            SelectManip.MODE_TGL: (QtCore.Qt.Key_Control,('cursor_t',0,0)),
        }[mode]
        GraphicsViewBoxManip.__init__(
            self, graphicsView,
            enableKey=k,
            enableCursor=c,
            buttons=(QtCore.Qt.LeftButton,),
            cursors=(c,), 
            name="Select_Manipulator"
        )
        self.mode = mode
        self.setPen( 
            QtGui.QPen(
                QtGui.QBrush( QtGui.QColor('#444488' ) ), 1#this was slow:, QtCore.Qt.DotLine,
            )
        )
        self.setBrush(
            QtGui.QBrush(
                QtGui.QColor( 255, 255, 255, 32 )
            )
        )
    
    def AllowActivation( self, event ):
        return self.GetMovable(event.pos()) is None
    
    def OnAfterBox( self, rect, items, event ):
        print self.mode
        if self.mode == SelectManip.MODE_STD:
            print 'STD'
            # TODO: UUUGGGLLYLY, use a selectModel instead
            graph_items.AssetItem.clearSelect()
            for item in items:
                item.setSelected( True )
        elif self.mode == SelectManip.MODE_ADD:
            print 'ADD'
            for item in items:
                item.setSelected( True )
        elif self.mode == SelectManip.MODE_TGL:
            print 'TGL'
            for item in items:
                item.toggleSelected()

class FrameManip( GraphicsViewBoxManip ):
    def __init__( self, graphicsView, mode=None ):
        GraphicsViewBoxManip.__init__(
            self, graphicsView,
            enableKey=None,
            enableCursor=None, 
            buttons=(QtCore.Qt.MidButton,),
            cursors=(("zoomIn",5,5),), 
            name="Frame_Manipulator"
        )
        self.mode = mode
        self.rectItem = None
        self.setPen( 
            QtGui.QPen(
                QtGui.QBrush( QtGui.QColor('#FFFFFF' ) ), 1#this was slow:, QtCore.Qt.DotLine,
            )
        )
        self.setBrush( 
            QtGui.QBrush(
                QtGui.QColor( 255, 255, 255, 32 )
            )
        )
    
    def OnAfterBox( self, rect, items, event ):
        if rect.width() < 10:
            self.getView().frameAll()
        else:
            self.getView().fitInView( rect, QtCore.Qt.KeepAspectRatio )

class GV( QtGui.QGraphicsView ):
    def __init__( self, parent ):
        QtGui.QGraphicsView.__init__( self, parent )
        
        #self.setFocusPolicy(QtCore.Qt.WheelFocus)
        #self.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarAlwaysOff )
        #self.setVerticalScrollBarPolicy( QtCore.Qt.ScrollBarAlwaysOff )
        self._activeManip = None
        self.setTransformationAnchor( QtGui.QGraphicsView.NoAnchor )
        self.setDragMode( QtGui.QGraphicsView.NoDrag )
        
        self.setBackgroundBrush(
            QtGui.QBrush( QtGui.QColor(56, 56, 56) ) 
        )
        
        QtGui.QShortcut( QtCore.Qt.Key_F, self, self.frameAll )
        
        self.installEventFilter( MoveManip(self) )
        self.installEventFilter( MoveManip(self,mode=MoveManip.MODE_UPS) )
        self.installEventFilter( MoveManip(self,mode=MoveManip.MODE_DOWNS) )
        self.installEventFilter( SelectManip(self) )
        self.installEventFilter( SelectManip(self,mode=SelectManip.MODE_ADD) )
        self.installEventFilter( SelectManip(self,mode=SelectManip.MODE_TGL) )
        self.installEventFilter( FrameManip(self) )
        self.installEventFilter( PanManip(self) )
        self.installEventFilter( ZoomManip(self) )
        
        #self.setSceneRect( 0, 0, 1000000, 1000000 )
    
    def setActiveManip( self, manipulator ):
        '''Called by a Manipulator when it enables on this view.'''
        self._activeManip = manipulator
    
    def activeManip( self ):
        '''Called by a Manipulator when it disables on this view.'''
        return self._activeManip
        
    def leaveEvent( self, event ):
        '''Desable the current active manipulator if one.'''
        print self._activeManip
        if self._activeManip:
            self._activeManip.ForceStop()
            self._activeManip = None
        return QtGui.QGraphicsView.leaveEvent( self, event )
        
    def frameAll( self, *args ):
        self.scene().root().resetTransform()
        print 'S:', self.scene().sceneRect()
        print 'R:', self.scene().root().sceneBoundingRect()
        self.fitInView(
            self.scene().root().sceneBoundingRect(), 
            QtCore.Qt.KeepAspectRatio 
        )
